package gameEngine;

import gameBasicElements.Board;
import gameBasicElements.Move;
import gameBasicElements.PlayerColor;
import gameBasicElements.Position;
import gameBasicElements.ScoreBoard;
import gameBasicElements.StandardBoard;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import rulesControl.Rules;

/**
 * @author Igor Adamski
 *
 */
public class SGFGame {
	public static class WrongSGFFileException extends Exception {
		public WrongSGFFileException() { super(); }
		public WrongSGFFileException(String str) { super(str); }
		private static final long serialVersionUID = 968606962291947691L;
	}
	public static class NoSuchVariationException extends Exception {
		private static final long serialVersionUID = -2109208050277861370L;
	}
	private static class TreeGameHistory {
		private static int debug = 0;
		private int id = debug++;
		@Override
		public String toString() { return String.valueOf(id); }
		private Map<String, List<String>> fields;
		private List<TreeGameHistory> variations;
		private TreeGameHistory parent;
		private Board board;
		private static int index;
		private static String str;
		private final int defaultBoardSize = 19;
		private TreeGameHistory() {
			fields = new TreeMap<String, List<String>>();
			variations = new ArrayList<TreeGameHistory>();
		}
		
		public TreeGameHistory getVariation(int i) throws NoSuchVariationException {
			if(i < variations.size())
				return variations.get(i);
			else
				throw new NoSuchVariationException();
		}
		public TreeGameHistory getParent() {
			return parent;
		}
		public void prepare() {
			board = new StandardBoard(fields.containsKey("SZ") ? Integer.valueOf(fields.get("SZ").get(0)) : defaultBoardSize);
			prepare(board);
				
		}
		private Position makePosition(String v) {
			if(v == null || v.equals(""))
				return null;
			int y = (v.charAt(0) - 'a');
			int x = (v.charAt(1) - 'a');
			if(x == board.getSize() && y == board.getSize())
				return null;
			return new Position(x, y);
		}
		private void prepare(Board b) {
			board = b;
			if(fields.containsKey("AB")) {
				for(String val : fields.get("AB"))
					board = board.placeStone(new Move(makePosition(val), PlayerColor.BLACK));
			}
			if(fields.containsKey("AW")) {
				for(String val : fields.get("AW"))
					board = board.placeStone(new Move(makePosition(val), PlayerColor.WHITE));
			}
			if(fields.containsKey("B")) {
				for(String val : fields.get("B"))
					board = board.update(new Move(makePosition(val), PlayerColor.BLACK));
			}
			if(fields.containsKey("W")) {
				for(String val : fields.get("W"))
					board = board.update(new Move(makePosition(val), PlayerColor.WHITE));
			}
			for(TreeGameHistory var : variations)
				var.prepare(board);
		}
		public Board getBoard() {
			return board;
		}
		public String getComment() {
			return fields.containsKey("C") ? fields.get("C").get(0) : "";
		}
		public int getVariationsNumber() {
			return variations.size();
		}
		public Board getVariationBoard(int i) throws NoSuchVariationException {
			if(i == -1) {
				if(parent == null)
					throw new NoSuchVariationException();
				return parent.getBoard();
			}
			if(i < variations.size())
				return variations.get(i).getBoard();
			else
				throw new NoSuchVariationException();
		}
		
		private void setParent(TreeGameHistory parent) {
			this.parent = parent;
		}
		private void addVariation(TreeGameHistory var) {
			var.setParent(this);
			variations.add(var);
		}
		private void readField() throws WrongSGFFileException {
			int start = index;
			while(index < str.length() && str.charAt(index) != '[')
				++index;
			if(index == str.length())
				throw new WrongSGFFileException("read field1 " + str);
			String fieldName = str.substring(start, index);
			if(!fields.containsKey(fieldName))
				fields.put(fieldName, new ArrayList<String>());
			while(str.charAt(index) == '[')
			{
				int begin = index+1;
				while(index < str.length() && str.charAt(index) != ']')
				{
					if(str.charAt(index) == '\\')
						++index;
					++index;
				}
				if(index >= str.length())
					throw new WrongSGFFileException("read field2");
				fields.get(fieldName).add(str.substring(begin, index));
				++index;
			}
		}
		// wchodzac na ';' wychodzac na ')'
		private static TreeGameHistory singleNode() throws WrongSGFFileException {
			TreeGameHistory ret = new TreeGameHistory();
			++index;
			while(str.charAt(index) != ';' && str.charAt(index) != '(' && str.charAt(index) != ')')
				ret.readField();
			if(str.charAt(index) == ';') {
				ret.addVariation(singleNode());
				return ret;
			}
			else if(str.charAt(index) == ')') {
				return ret;
			}
			else {
				while(str.charAt(index) == '(')
					ret.addVariation(createTreeGameHistory());
			}
			return ret;
		}
		// wchodzac na( - wychodzac jest za )
		private static TreeGameHistory createTreeGameHistory() throws WrongSGFFileException {
			if(str.charAt(index++) != '(')
				throw new WrongSGFFileException("nawias otwierajacy: ");
			TreeGameHistory ret = new TreeGameHistory();
			while(index < str.length() && str.charAt(index) != ')')
			{
				if(str.charAt(index) == '(') {
					ret.addVariation(createTreeGameHistory());
				}
				else if(str.charAt(index) == ';') {
					ret = singleNode();
					++index;
					return ret;
				}
				else
					throw new WrongSGFFileException("zly znak");
			}
			if(index >= str.length())
				throw new WrongSGFFileException("dsa");
			++index;
			return ret;
			
		}
		public static TreeGameHistory makeTreeGameHistory(String str) throws WrongSGFFileException {
			index = 0;
			TreeGameHistory.str = str;
			return createTreeGameHistory();
		}
	}
	private TreeGameHistory treeHistory;
	public SGFGame(String sgf) throws WrongSGFFileException {
		StringBuilder str = new StringBuilder();
		int tmp = 0;
		for(int i=0;i<sgf.length();++i)
		{
			if(!Character.isWhitespace(sgf.charAt(i)) || tmp > 0)
				str.append(sgf.charAt(i));
			if(sgf.charAt(i) == '[')
				++tmp;
			if(sgf.charAt(i) == ']')
				--tmp;
		}
		if(str.toString().equals(""))
			throw new WrongSGFFileException();
		treeHistory = TreeGameHistory.makeTreeGameHistory(str.toString());
		treeHistory.prepare();
	}
	
	public void goVariation(int i) throws NoSuchVariationException {
		treeHistory = treeHistory.getVariation(i);
	}
	public Board getBoard() {
		return treeHistory.getBoard();
	}
	public void goBack() throws NoSuchVariationException {
		if(treeHistory.getParent() == null)
			throw new NoSuchVariationException();
		treeHistory = treeHistory.getParent();
	}
	public String getComment() {
		return treeHistory.getComment();
	}
	public int getVariationsNumber() {
		return treeHistory.getVariationsNumber();
	}
	public Board getVariationBoard(int i) throws NoSuchVariationException {
		return treeHistory.getVariationBoard(i);
	}
	private static String getCoords(Position pos) {
		if(pos == null)
			return "";
		char y = (char)(pos.getX() + 'a');
		char x = (char)(pos.getY() + 'a');
		return String.valueOf(x) + String.valueOf(y);
	}
	
	public static String createSGFString(Rules rules, GameHistory history) {
		// header
		StringBuilder ret = new StringBuilder("(;FF[4]GM[1]");
		// board size
		ret.append("SZ[" + rules.getBoard().getSize() + "]");
		// players names
		ret.append("PB[Black]").append("PW[White]");
		// handicap
		ret.append("HA[" + rules.getHandicap().getHandicapStones() + "]");
		// komi
		ret.append("KM[" + rules.getHandicap().getKomi() + "]");
		// time
		ret.append("TM[" + rules.getByoYomi().getInitTime()/1000 + "]");
		// results
		ret.append("RE[");
		ScoreBoard results = rules.getScores(history.getBoardAtTurn(Math.max(0, history.getCurrentTurn()-1)));
		double dif = results.getBlackPlayerScore() - results.getWhitePlayerScore();
		if(dif == 0.0)
			ret.append("0");
		else {
			if(dif > 0.0)
				ret.append("B+" + dif);
			else
				ret.append("W+" + (-dif));
		}
		ret.append("]");
		
		for(int i=1;i<=rules.getHandicap().getHandicapStones();++i)
		{
			ret.append(";AB[" +  getCoords(history.getMoveAtTurn(i).getPosition()) + "]");
		}
		for(int i=rules.getHandicap().getHandicapStones()+1;i<history.getCurrentTurn();++i)
		{
			ret.append(";" + history.getMoveAtTurn(i).getColor().toString().charAt(0) + "[" + getCoords(history.getMoveAtTurn(i).getPosition()) + "]");
		}
		// end
		ret.append(")");
		return ret.toString();
	}
	
}
